<script>
export default {
  components: {},
  data () {
    return {
      radio: 0,
      activeName: '1',
    };
  },
  computed: {
    isButtonDisabled() {
      return this.radio === 0;
    }
  },
  methods: {
    check(radio) {
      console.log(radio)
      if (radio === 3) {
        this.$message.success('恭喜你，答对啦！')
      } else {
        this.$message.error('很遗憾，答错啦！')
      }
    },
  }
}
</script>

<template>
  <div>
    <div>
      <h1 align="center">2.1 Linux 内核调试</h1>
      <el-divider></el-divider>
      <h3>准备工作</h3>
      与用户态程序不同，为了进行内核调试，我们需要两台机器，一台调试，另一台被调试。在调试机上需要安装必要的调试器（如GDB），被调试机上运行着被调试的内核。
      <br>
      这里选择用 Ubuntu16.04 来展示，因为该发行版默认已经开启了内核调试支持：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ cat </span><span class="pun">/</span><span class="pln">boot</span><span class="pun">/</span><span class="pln">config</span><span class="pun">-</span><span class="lit">4.13</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">38</span><span class="pun">-</span><span class="kwd">generic</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> grep GDB</span></code></li><li class="L1"><code class="lang-text"><span class="com"># CONFIG_CFG80211_INTERNAL_REGDB is not set</span></code></li><li class="L2"><code class="lang-text"><span class="pln">CONFIG_SERIAL_KGDB_NMI</span><span class="pun">=</span><span class="pln">y</span></code></li><li class="L3"><code class="lang-text"><span class="pln">CONFIG_GDB_SCRIPTS</span><span class="pun">=</span><span class="pln">y</span></code></li><li class="L4"><code class="lang-text"><span class="pln">CONFIG_HAVE_ARCH_KGDB</span><span class="pun">=</span><span class="pln">y</span></code></li><li class="L5"><code class="lang-text"><span class="pln">CONFIG_KGDB</span><span class="pun">=</span><span class="pln">y</span></code></li><li class="L6"><code class="lang-text"><span class="pln">CONFIG_KGDB_SERIAL_CONSOLE</span><span class="pun">=</span><span class="pln">y</span></code></li><li class="L7"><code class="lang-text"><span class="com"># CONFIG_KGDB_TESTS is not set</span></code></li><li class="L8"><code class="lang-text"><span class="pln">CONFIG_KGDB_LOW_LEVEL_TRAP</span><span class="pun">=</span><span class="pln">y</span></code></li><li class="L9"><code class="lang-text"><span class="pln">CONFIG_KGDB_KDB</span><span class="pun">=</span><span class="pln">y</span></code></li></ol></pre>
      <b>获取符号文件</b><br>
      下面我们来准备调试需要的符号文件。看一下该版本的 code name：
      <br>
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ lsb_release </span><span class="pun">-</span><span class="pln">c</span></code></li><li class="L1"><code class="lang-text"><span class="typ">Codename</span><span class="pun">:</span><span class="pln"> xenial</span></code></li></ol></pre>
      然后在下面的目录下新建文件 ddebs.list，其内容如下（注意看情况修改Codename）：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ cat </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">apt</span><span class="pun">/</span><span class="pln">sources</span><span class="pun">.</span><span class="pln">list</span><span class="pun">.</span><span class="pln">d</span><span class="pun">/</span><span class="pln">ddebs</span><span class="pun">.</span><span class="pln">list</span></code></li><li class="L1"><code class="lang-text"><span class="pln">deb http</span><span class="pun">:</span><span class="com">//ddebs.ubuntu.com/ xenial      main restricted universe multiverse</span></code></li><li class="L2"><code class="lang-text"><span class="pln">deb http</span><span class="pun">:</span><span class="com">//ddebs.ubuntu.com/ xenial-security main restricted universe multiverse</span></code></li><li class="L3"><code class="lang-text"><span class="pln">deb http</span><span class="pun">:</span><span class="com">//ddebs.ubuntu.com/ xenial-updates  main restricted universe multiverse</span></code></li><li class="L4"><code class="lang-text"><span class="pln">deb http</span><span class="pun">:</span><span class="com">//ddebs.ubuntu.com/ xenial-proposed main restricted universe multiverse</span></code></li></ol></pre>
      http://ddebs.ubuntu.com 是 Ubuntu 的符号服务器。执行下面的命令添加密钥：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ wget </span><span class="pun">-</span><span class="pln">O </span><span class="pun">-</span><span class="pln"> http</span><span class="pun">:</span><span class="com">//ddebs.ubuntu.com/dbgsym-release-key.asc | sudo apt-key add -</span></code></li></ol></pre>
      然后就可以更新并下载符号文件了：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update</span></code></li><li class="L1"><code class="lang-text"><span class="pln">$ uname </span><span class="pun">-</span><span class="pln">r</span></code></li><li class="L2"><code class="lang-text"><span class="lit">4.13</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">38</span><span class="pun">-</span><span class="kwd">generic</span></code></li><li class="L3"><code class="lang-text"><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install linux</span><span class="pun">-</span><span class="pln">image</span><span class="pun">-</span><span class="lit">4.13</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">38</span><span class="pun">-</span><span class="kwd">generic</span><span class="pun">-</span><span class="pln">dbgsym</span></code></li></ol></pre>
      完成后，符号文件将会放在下面的目录下：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ file </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">debug</span><span class="pun">/</span><span class="pln">boot</span><span class="pun">/</span><span class="pln">vmlinux</span><span class="pun">-</span><span class="lit">4.13</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">38</span><span class="pun">-</span><span class="kwd">generic</span></code></li><li class="L1"><code class="lang-text"><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">debug</span><span class="pun">/</span><span class="pln">boot</span><span class="pun">/</span><span class="pln">vmlinux</span><span class="pun">-</span><span class="lit">4.13</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">38</span><span class="pun">-</span><span class="kwd">generic</span><span class="pun">:</span><span class="pln"> ELF </span><span class="lit">64</span><span class="pun">-</span><span class="pln">bit LSB executable</span><span class="pun">,</span><span class="pln"> x86</span><span class="pun">-</span><span class="lit">64</span><span class="pun">,</span><span class="pln"> version </span><span class="lit">1</span><span class="pln"> </span><span class="pun">(</span><span class="pln">SYSV</span><span class="pun">),</span><span class="pln"> statically linked</span><span class="pun">,</span><span class="pln"> </span><span class="typ">BuildID</span><span class="pun">[</span><span class="pln">sha1</span><span class="pun">]=</span><span class="pln">f00f4b7ef0ab8fa738b6a9caee91b2cbe23fef97</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> stripped</span></code></li></ol></pre>
      可以看到这是一个静态链接的可执行文件，使用 gdb 即可进行调试，例如这样：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ gdb </span><span class="pun">-</span><span class="pln">q </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">debug</span><span class="pun">/</span><span class="pln">boot</span><span class="pun">/</span><span class="pln">vmlinux</span><span class="pun">-</span><span class="lit">4.13</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">38</span><span class="pun">-</span><span class="kwd">generic</span></code></li><li class="L1"><code class="lang-text"><span class="typ">Reading</span><span class="pln"> symbols </span><span class="kwd">from</span><span class="pln"> </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">debug</span><span class="pun">/</span><span class="pln">boot</span><span class="pun">/</span><span class="pln">vmlinux</span><span class="pun">-</span><span class="lit">4.13</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">38</span><span class="pun">-</span><span class="kwd">generic</span><span class="pun">...</span><span class="kwd">done</span><span class="pun">.</span></code></li><li class="L2"><code class="lang-text"><span class="pln">gdb</span><span class="pun">-</span><span class="pln">peda$ p init_uts_ns</span></code></li><li class="L3"><code class="lang-text"><span class="pln">$1 </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L4"><code class="lang-text"><span class="pln">  kref </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L5"><code class="lang-text"><span class="pln">    refcount </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L6"><code class="lang-text"><span class="pln">      refs </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code class="lang-text"><span class="pln">        counter </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2</span></code></li><li class="L8"><code class="lang-text"><span class="pln">      </span><span class="pun">}</span></code></li><li class="L9"><code class="lang-text"><span class="pln">    </span><span class="pun">}</span></code></li><li class="L0"><code class="lang-text"><span class="pln">  </span><span class="pun">},</span></code></li><li class="L1"><code class="lang-text"><span class="pln">  name </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code class="lang-text"><span class="pln">    sysname </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Linux"</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\000'</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">repeats </span><span class="lit">59</span><span class="pln"> times</span><span class="pun">&gt;,</span></code></li><li class="L3"><code class="lang-text"><span class="pln">    nodename </span><span class="pun">=</span><span class="pln"> </span><span class="str">"(none)"</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\000'</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">repeats </span><span class="lit">58</span><span class="pln"> times</span><span class="pun">&gt;,</span></code></li><li class="L4"><code class="lang-text"><span class="pln">    release </span><span class="pun">=</span><span class="pln"> </span><span class="str">"4.13.0-38-generic"</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\000'</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">repeats </span><span class="lit">47</span><span class="pln"> times</span><span class="pun">&gt;,</span></code></li><li class="L5"><code class="lang-text"><span class="pln">    version </span><span class="pun">=</span><span class="pln"> </span><span class="str">"#43~16.04.1-Ubuntu SMP Wed Mar 14 17:48:43 UTC 2018"</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\000'</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">repeats </span><span class="lit">13</span><span class="pln"> times</span><span class="pun">&gt;,</span></code></li><li class="L6"><code class="lang-text"><span class="pln">    machine </span><span class="pun">=</span><span class="pln"> </span><span class="str">"x86_64"</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\000'</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">repeats </span><span class="lit">58</span><span class="pln"> times</span><span class="pun">&gt;,</span></code></li><li class="L7"><code class="lang-text"><span class="pln">    domainname </span><span class="pun">=</span><span class="pln"> </span><span class="str">"(none)"</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\000'</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">repeats </span><span class="lit">58</span><span class="pln"> times</span><span class="pun">&gt;</span></code></li><li class="L8"><code class="lang-text"><span class="pln">  </span><span class="pun">},</span></code></li><li class="L9"><code class="lang-text"><span class="pln">  user_ns </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xffffffff822517a0</span><span class="pln"> </span><span class="str">&lt;init_user_ns&gt;</span><span class="pun">,</span></code></li><li class="L0"><code class="lang-text"><span class="pln">  ucounts </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pln"> </span><span class="str">&lt;irq_stack_union&gt;</span><span class="pun">,</span></code></li><li class="L1"><code class="lang-text"><span class="pln">  ns </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code class="lang-text"><span class="pln">    stashed </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-text"><span class="pln">      counter </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L4"><code class="lang-text"><span class="pln">    </span><span class="pun">},</span></code></li><li class="L5"><code class="lang-text"><span class="pln">    ops </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xffffffff81e2cc80</span><span class="pln"> </span><span class="str">&lt;utsns_operations&gt;</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-text"><span class="pln">    inum </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xeffffffe</span></code></li><li class="L7"><code class="lang-text"><span class="pln">  </span><span class="pun">}</span></code></li><li class="L8"><code class="lang-text"><span class="pun">}</span></code></li></ol></pre>
      <b>获取源文件</b>
      <br>
      将 /etc/apt/sources.list 里的 deb-src 行都取消掉注释：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ sed </span><span class="pun">-</span><span class="pln">i </span><span class="str">'/^#\sdeb-src /s/^#//'</span><span class="pln"> </span><span class="str">"/etc/apt/sources.list"</span></code></li></ol></pre>
      然后就可以更新并获取 Linux 内核源文件了：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update</span></code></li><li class="L1"><code class="lang-text"><span class="pln">$ mkdir </span><span class="pun">-</span><span class="pln">p </span><span class="pun">~</span><span class="str">/kernel/</span><span class="pln">source</span></code></li><li class="L2"><code class="lang-text"><span class="pln">$ cd </span><span class="pun">~</span><span class="str">/kernel/</span><span class="pln">source</span></code></li><li class="L3"><code class="lang-text"><span class="pln">$ apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> source $</span><span class="pun">(</span><span class="pln">dpkg</span><span class="pun">-</span><span class="pln">query </span><span class="str">'--showformat=${source:Package}=${source:Version}'</span><span class="pln"> </span><span class="pun">--</span><span class="pln">show linux</span><span class="pun">-</span><span class="pln">image</span><span class="pun">-</span><span class="pln">$</span><span class="pun">(</span><span class="pln">uname </span><span class="pun">-</span><span class="pln">r</span><span class="pun">))</span></code></li></ol></pre>
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ ls linux</span><span class="pun">-</span><span class="pln">hwe</span><span class="pun">-</span><span class="lit">4.13</span><span class="pun">.</span><span class="lit">0</span><span class="pun">/</span></code></li><li class="L1"><code class="lang-text"><span class="pln">arch     CREDITS     debian</span><span class="pun">.</span><span class="pln">master  firmware  ipc      lib          net      security        tools   zfs</span></code></li><li class="L2"><code class="lang-text"><span class="pln">block    crypto      </span><span class="typ">Documentation</span><span class="pln">  fs        </span><span class="typ">Kbuild</span><span class="pln">   MAINTAINERS  README   snapcraft</span><span class="pun">.</span><span class="pln">yaml  ubuntu</span></code></li><li class="L3"><code class="lang-text"><span class="pln">certs    debian      drivers        include   </span><span class="typ">Kconfig</span><span class="pln">  </span><span class="typ">Makefile</span><span class="pln">     samples  sound           usr</span></code></li><li class="L4"><code class="lang-text"><span class="pln">COPYING  debian</span><span class="pun">.</span><span class="pln">hwe  dropped</span><span class="pun">.</span><span class="pln">txt    init      kernel   mm           scripts  spl</span></code></li></ol></pre>
      <b>printk</b>
      <br>在用户态程序中，我们常常使用 printf() 来打印信息，方便调试，在内核中也可以这样做。内核（v4.16.3）使用函数 printk() 来输出信息，在 include/linux/kern_levels.h 中定义了 8 个级别：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-c"><span class="com">#define</span><span class="pln"> KERN_EMERG  KERN_SOH </span><span class="str">"0"</span><span class="pln">  </span><span class="com">/* system is unusable */</span></code></li><li class="L1"><code class="lang-c"><span class="com">#define</span><span class="pln"> KERN_ALERT  KERN_SOH </span><span class="str">"1"</span><span class="pln">  </span><span class="com">/* action must be taken immediately */</span></code></li><li class="L2"><code class="lang-c"><span class="com">#define</span><span class="pln"> KERN_CRIT KERN_SOH </span><span class="str">"2"</span><span class="pln">  </span><span class="com">/* critical conditions */</span></code></li><li class="L3"><code class="lang-c"><span class="com">#define</span><span class="pln"> KERN_ERR  KERN_SOH </span><span class="str">"3"</span><span class="pln">  </span><span class="com">/* error conditions */</span></code></li><li class="L4"><code class="lang-c"><span class="com">#define</span><span class="pln"> KERN_WARNING  KERN_SOH </span><span class="str">"4"</span><span class="pln">  </span><span class="com">/* warning conditions */</span></code></li><li class="L5"><code class="lang-c"><span class="com">#define</span><span class="pln"> KERN_NOTICE KERN_SOH </span><span class="str">"5"</span><span class="pln">  </span><span class="com">/* normal but significant condition */</span></code></li><li class="L6"><code class="lang-c"><span class="com">#define</span><span class="pln"> KERN_INFO KERN_SOH </span><span class="str">"6"</span><span class="pln">  </span><span class="com">/* informational */</span></code></li><li class="L7"><code class="lang-c"><span class="com">#define</span><span class="pln"> KERN_DEBUG  KERN_SOH </span><span class="str">"7"</span><span class="pln">  </span><span class="com">/* debug-level messages */</span></code></li></ol></pre>
      用法是：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-c"><span class="pln">printk</span><span class="pun">(</span><span class="pln">KERN_EMERG </span><span class="str">"hello world!\n"</span><span class="pun">);</span><span class="pln">  </span><span class="com">// 中间没有逗号</span></code></li></ol></pre>
      而当前控制台的日志级别如下所示：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ cat </span><span class="pun">/</span><span class="pln">proc</span><span class="pun">/</span><span class="pln">sys</span><span class="pun">/</span><span class="pln">kernel</span><span class="pun">/</span><span class="pln">printk</span></code></li><li class="L1"><code class="lang-text"><span class="lit">4</span><span class="pln">       </span><span class="lit">4</span><span class="pln">       </span><span class="lit">1</span><span class="pln">       </span><span class="lit">4</span></code></li></ol></pre>
      这 4 个数值在文件定义及默认值在如下所示：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-c"><span class="com">// kernel/printk/printk.c</span></code></li><li class="L1"><code class="lang-c"></code></li><li class="L2"><code class="lang-c"><span class="typ">int</span><span class="pln"> console_printk</span><span class="pun">[</span><span class="lit">4</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-c"><span class="pln">  CONSOLE_LOGLEVEL_DEFAULT</span><span class="pun">,</span><span class="pln">    </span><span class="com">/* console_loglevel */</span></code></li><li class="L4"><code class="lang-c"><span class="pln">  MESSAGE_LOGLEVEL_DEFAULT</span><span class="pun">,</span><span class="pln">    </span><span class="com">/* default_message_loglevel */</span></code></li><li class="L5"><code class="lang-c"><span class="pln">  CONSOLE_LOGLEVEL_MIN</span><span class="pun">,</span><span class="pln">        </span><span class="com">/* minimum_console_loglevel */</span></code></li><li class="L6"><code class="lang-c"><span class="pln">  CONSOLE_LOGLEVEL_DEFAULT</span><span class="pun">,</span><span class="pln">    </span><span class="com">/* default_console_loglevel */</span></code></li><li class="L7"><code class="lang-c"><span class="pun">};</span></code></li><li class="L8"><code class="lang-c"></code></li><li class="L9"><code class="lang-c"></code></li><li class="L0"><code class="lang-c"><span class="com">// include/linux/printk.h</span></code></li><li class="L1"><code class="lang-c"></code></li><li class="L2"><code class="lang-c"><span class="com">/* printk's without a loglevel use this.. */</span></code></li><li class="L3"><code class="lang-c"><span class="com">#define</span><span class="pln"> MESSAGE_LOGLEVEL_DEFAULT CONFIG_MESSAGE_LOGLEVEL_DEFAULT</span></code></li><li class="L4"><code class="lang-c"></code></li><li class="L5"><code class="lang-c"><span class="com">/* We show everything that is MORE important than this.. */</span></code></li><li class="L6"><code class="lang-c"><span class="com">#define</span><span class="pln"> CONSOLE_LOGLEVEL_MIN     </span><span class="lit">1</span><span class="pln"> </span><span class="com">/* Minimum loglevel we let people use */</span></code></li><li class="L7"><code class="lang-c"></code></li><li class="L8"><code class="lang-c"><span class="com">/*</span></code></li><li class="L9"><code class="lang-c"><span class="com"> * Default used to be hard-coded at 7, we're now allowing it to be set from</span></code></li><li class="L0"><code class="lang-c"><span class="com"> * kernel config.</span></code></li><li class="L1"><code class="lang-c"><span class="com"> */</span></code></li><li class="L2"><code class="lang-c"><span class="com">#define</span><span class="pln"> CONSOLE_LOGLEVEL_DEFAULT CONFIG_CONSOLE_LOGLEVEL_DEFAULT</span></code></li><li class="L3"><code class="lang-c"></code></li><li class="L4"><code class="lang-c"><span class="com">#define</span><span class="pln"> console_loglevel </span><span class="pun">(</span><span class="pln">console_printk</span><span class="pun">[</span><span class="lit">0</span><span class="pun">])</span></code></li><li class="L5"><code class="lang-c"><span class="com">#define</span><span class="pln"> default_message_loglevel </span><span class="pun">(</span><span class="pln">console_printk</span><span class="pun">[</span><span class="lit">1</span><span class="pun">])</span></code></li><li class="L6"><code class="lang-c"><span class="com">#define</span><span class="pln"> minimum_console_loglevel </span><span class="pun">(</span><span class="pln">console_printk</span><span class="pun">[</span><span class="lit">2</span><span class="pun">])</span></code></li><li class="L7"><code class="lang-c"><span class="com">#define</span><span class="pln"> default_console_loglevel </span><span class="pun">(</span><span class="pln">console_printk</span><span class="pun">[</span><span class="lit">3</span><span class="pun">])</span></code></li></ol></pre>
      虽然这些数值控制了当前控制台的日志级别，但使用虚拟文件 /proc/kmsg 或者命令 dmesg 总是可以查看所有的信息。

      <h3>QEMU + gdb</h3>
      QEMU 是一款开源的虚拟机软件，可以使用它模拟出一个完整的操作系统（参考章节2.1.1）。这里我们介绍怎样使用 QEMU 和 gdb 进行内核调试
      <br>接下来我们需要借助 BusyBox 来创建用户空间：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ wget </span><span class="pun">-</span><span class="pln">c http</span><span class="pun">:</span><span class="com">//busybox.net/downloads/busybox-1.28.3.tar.bz2</span></code></li><li class="L1"><code class="lang-text"><span class="pln">$ tar </span><span class="pun">-</span><span class="pln">xvjf busybox</span><span class="pun">-</span><span class="lit">1.28</span><span class="pun">.</span><span class="lit">3.tar</span><span class="pun">.</span><span class="pln">bz2</span></code></li><li class="L2"><code class="lang-text"><span class="pln">$ cd busybox</span><span class="pun">-</span><span class="lit">1.28</span><span class="pun">.</span><span class="lit">3</span><span class="pun">/</span></code></li></ol></pre>
      生成默认配置文件并修改 CONFIG_STATIC=y 让它生成的是一个静态链接的 BusyBox，这是因为 qemu 中没有动态链接库：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ make defconfig</span></code></li><li class="L1"><code class="lang-text"><span class="pln">$ cat </span><span class="pun">.</span><span class="pln">config </span><span class="pun">|</span><span class="pln"> grep </span><span class="str">"CONFIG_STATIC"</span></code></li><li class="L2"><code class="lang-text"><span class="pln">CONFIG_STATIC</span><span class="pun">=</span><span class="pln">y</span></code></li></ol></pre>
      编译安装后会出现在 _install 目录下：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ make</span></code></li><li class="L1"><code class="lang-text"><span class="pln">$ sudo make install</span></code></li><li class="L2"><code class="lang-text"><span class="pln">$ ls _install</span></code></li><li class="L3"><code class="lang-text"><span class="pln">bin  linuxrc  sbin  usr</span></code></li></ol></pre>
      接下来创建 initramfs 的目录结构：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ mkdir initramfs</span></code></li><li class="L1"><code class="lang-text"><span class="pln">$ cd initramfs</span></code></li><li class="L2"><code class="lang-text"><span class="pln">$ cp </span><span class="pun">../</span><span class="pln">_install</span><span class="com">/* -rf ./</span></code></li><li class="L3"><code class="lang-text"><span class="com">$ mkdir dev proc sys</span></code></li><li class="L4"><code class="lang-text"><span class="com">$ sudo cp -a /dev/null /dev/console /dev/tty /dev/tty2 /dev/tty3 /dev/tty4 dev/</span></code></li><li class="L5"><code class="lang-text"><span class="com">$ rm linuxrc</span></code></li><li class="L6"><code class="lang-text"><span class="com">$ vim init      # 创建启动脚本</span></code></li><li class="L7"><code class="lang-text"><span class="com">$ cat init</span></code></li><li class="L8"><code class="lang-text"><span class="com">#!/bin/busybox sh</span></code></li><li class="L9"><code class="lang-text"><span class="com">mount -t proc none /proc</span></code></li><li class="L0"><code class="lang-text"><span class="com">mount -t sysfs none /sys</span></code></li><li class="L1"><code class="lang-text"></code></li><li class="L2"><code class="lang-text"><span class="com">exec /sbin/init</span></code></li></ol></pre>
      最后把它们打包：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ find </span><span class="pun">.</span><span class="pln"> </span><span class="pun">-</span><span class="pln">print0 </span><span class="pun">|</span><span class="pln"> cpio </span><span class="pun">--</span><span class="kwd">null</span><span class="pln"> </span><span class="pun">-</span><span class="pln">ov </span><span class="pun">--</span><span class="pln">format</span><span class="pun">=</span><span class="pln">newc </span><span class="pun">|</span><span class="pln"> gzip </span><span class="pun">-</span><span class="lit">9</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">../</span><span class="pln">initramfs</span><span class="pun">.</span><span class="pln">cpio</span><span class="pun">.</span><span class="pln">gz</span></code></li></ol></pre>
      这样 initramfs 根文件系统就做好了，其中包含了必要的设备驱动和工具，boot loader 会加载 initramfs 到内存，然后内核将其挂载到根目录 /，并运行 init 脚本，挂载真正的磁盘根文件系统。
      <br>QEMU 启动!
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ qemu</span><span class="pun">-</span><span class="pln">system</span><span class="pun">-</span><span class="pln">x86_64 </span><span class="pun">-</span><span class="pln">s </span><span class="pun">-</span><span class="pln">S </span><span class="pun">-</span><span class="pln">kernel </span><span class="pun">~</span><span class="str">/kernelbuild/</span><span class="pln">linux</span><span class="pun">-</span><span class="lit">4.16</span><span class="pun">.</span><span class="lit">3</span><span class="pun">/</span><span class="pln">arch</span><span class="pun">/</span><span class="pln">x86_64</span><span class="pun">/</span><span class="pln">boot</span><span class="pun">/</span><span class="pln">bzImage </span><span class="pun">-</span><span class="pln">initrd </span><span class="pun">~</span><span class="str">/kernelbuild/</span><span class="pln">busybox</span><span class="pun">-</span><span class="lit">1.28</span><span class="pun">.</span><span class="lit">3</span><span class="pun">/</span><span class="pln">initramfs</span><span class="pun">.</span><span class="pln">cpio</span><span class="pun">.</span><span class="pln">gz </span><span class="pun">-</span><span class="pln">nographic </span><span class="pun">-</span><span class="pln">append </span><span class="str">"console=ttyS0"</span></code></li></ol></pre>
      <ul>
        <li style="margin-left: 2em;">-s：-gdb tcp::1234 的缩写，QEMU 监听在 TCP 端口 1234，等待 gdb 的连接。</li>
        <li style="margin-left: 2em;">-S：在启动时冻结 CPU，等待 gdb 输入 c 时继续执行。</li>
        <li style="margin-left: 2em;">-kernel：指定内核。</li>
        <li style="margin-left: 2em;">-initrd：指定 initramfs。</li>
        <li style="margin-left: 2em;">nographic：禁用图形输出并将串行 I/O 重定向到控制台。</li>
        <li style="margin-left: 2em;">-append "console=ttyS0：所有内核输出到 ttyS0 串行控制台，并打印到终端。</li>
      </ul>
      在另一个终端里使用打开 gdb，然后尝试在函数 cmdline_proc_show() 处下断点：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ gdb </span><span class="pun">-</span><span class="pln">ex </span><span class="str">"target remote localhost:1234"</span><span class="pln"> </span><span class="pun">~</span><span class="str">/kernelbuild/</span><span class="pln">linux</span><span class="pun">-</span><span class="lit">4.16</span><span class="pun">.</span><span class="lit">3</span><span class="pun">/</span><span class="pln">vmlinux</span></code></li><li class="L1"><code class="lang-text"><span class="pun">(</span><span class="pln">gdb</span><span class="pun">)</span><span class="pln"> b cmdline_proc_show</span></code></li><li class="L2"><code class="lang-text"><span class="typ">Breakpoint</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> at </span><span class="lit">0xffffffff8121ad70</span><span class="pun">:</span><span class="pln"> file fs</span><span class="pun">/</span><span class="pln">proc</span><span class="pun">/</span><span class="pln">cmdline</span><span class="pun">.</span><span class="pln">c</span><span class="pun">,</span><span class="pln"> line </span><span class="lit">9.</span></code></li><li class="L3"><code class="lang-text"><span class="pun">(</span><span class="pln">gdb</span><span class="pun">)</span><span class="pln"> c</span></code></li><li class="L4"><code class="lang-text"><span class="typ">Continuing</span><span class="pun">.</span></code></li><li class="L5"><code class="lang-text"></code></li><li class="L6"><code class="lang-text"><span class="typ">Breakpoint</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> cmdline_proc_show </span><span class="pun">(</span><span class="pln">m</span><span class="pun">=</span><span class="lit">0xffff880006701b00</span><span class="pun">,</span><span class="pln"> v</span><span class="pun">=</span><span class="lit">0x1</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">irq_stack_union</span><span class="pun">+</span><span class="lit">1</span><span class="pun">&gt;)</span><span class="pln"> at fs</span><span class="pun">/</span><span class="pln">proc</span><span class="pun">/</span><span class="pln">cmdline</span><span class="pun">.</span><span class="pln">c</span><span class="pun">:</span><span class="lit">9</span></code></li><li class="L7"><code class="lang-text"><span class="lit">9</span><span class="pln">               seq_printf</span><span class="pun">(</span><span class="pln">m</span><span class="pun">,</span><span class="pln"> </span><span class="str">"%s\n"</span><span class="pun">,</span><span class="pln"> saved_command_line</span><span class="pun">);</span></code></li></ol></pre>
      可以看到，当我们在内核里执行 cat /proc/cmdline 时就被断下来了。
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="com"># id</span></code></li><li class="L1"><code class="lang-text"><span class="pln">uid</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> gid</span><span class="pun">=</span><span class="lit">0</span></code></li><li class="L2"><code class="lang-text"><span class="com"># echo hello kernel!</span></code></li><li class="L3"><code class="lang-text"><span class="pln">hello kernel</span><span class="pun">!</span></code></li><li class="L4"><code class="lang-text"><span class="com"># cat /proc/cmdline</span></code></li><li class="L5"><code class="lang-text"><span class="pln">console</span><span class="pun">=</span><span class="pln">ttyS0</span></code></li></ol></pre>
      现在我们已经可以对内核代码进行单步调试了。对于内核模块，我们同样可以进行调试，但模块是动态加载的，gdb 不会知道这些模块被加载到哪里，所以需要使用 add-symbol-file 命令来告诉它。
      <br>来看一个 helloworld 的例子，源码：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-c"><span class="com">#include</span><span class="pln"> </span><span class="str">&lt;linux/init.h&gt;</span></code></li><li class="L1"><code class="lang-c"><span class="com">#include</span><span class="pln"> </span><span class="str">&lt;linux/module.h&gt;</span></code></li><li class="L2"><code class="lang-c"><span class="com">#include</span><span class="pln"> </span><span class="str">&lt;linux/kernel.h&gt;</span></code></li><li class="L3"><code class="lang-c"></code></li><li class="L4"><code class="lang-c"><span class="kwd">static</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> hello_init</span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span></code></li><li class="L5"><code class="lang-c"><span class="pun">{</span></code></li><li class="L6"><code class="lang-c"><span class="pln">        printk</span><span class="pun">(</span><span class="pln">KERN_ALERT </span><span class="str">"Hello module!\n"</span><span class="pun">);</span></code></li><li class="L7"><code class="lang-c"><span class="pln">        </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L8"><code class="lang-c"><span class="pun">}</span></code></li><li class="L9"><code class="lang-c"></code></li><li class="L0"><code class="lang-c"><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span><span class="pln"> hello_exit</span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-c"><span class="pun">{</span></code></li><li class="L2"><code class="lang-c"><span class="pln">        printk</span><span class="pun">(</span><span class="pln">KERN_ALERT </span><span class="str">"Goodbye module!\n"</span><span class="pun">);</span></code></li><li class="L3"><code class="lang-c"><span class="pun">}</span></code></li><li class="L4"><code class="lang-c"></code></li><li class="L5"><code class="lang-c"><span class="pln">module_init</span><span class="pun">(</span><span class="pln">hello_init</span><span class="pun">);</span></code></li><li class="L6"><code class="lang-c"><span class="pln">module_exit</span><span class="pun">(</span><span class="pln">hello_exit</span><span class="pun">);</span></code></li><li class="L7"><code class="lang-c"></code></li><li class="L8"><code class="lang-c"><span class="pln">MODULE_LICENSE</span><span class="pun">(</span><span class="str">"GPL"</span><span class="pun">);</span></code></li><li class="L9"><code class="lang-c"><span class="pln">MODULE_DESCRIPTION</span><span class="pun">(</span><span class="str">"A simple module."</span><span class="pun">);</span></code></li></ol></pre>
      Makefile 如下：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-makefile"><span class="pln">BUILDPATH </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">~</span><span class="str">/kernelbuild/</span><span class="pln">linux</span><span class="pun">-</span><span class="lit">4.16</span><span class="pun">.</span><span class="lit">3</span><span class="pun">/</span></code></li><li class="L1"><code class="lang-makefile"><span class="pln">obj</span><span class="pun">-</span><span class="pln">m </span><span class="pun">+=</span><span class="pln"> hello</span><span class="pun">.</span><span class="pln">o</span></code></li><li class="L2"><code class="lang-makefile"></code></li><li class="L3"><code class="lang-makefile"><span class="pln">all</span><span class="pun">:</span></code></li><li class="L4"><code class="lang-makefile"><span class="pln">        make </span><span class="pun">-</span><span class="pln">C $</span><span class="pun">(</span><span class="pln">BUILDPATH</span><span class="pun">)</span><span class="pln"> M</span><span class="pun">=</span><span class="pln">$</span><span class="pun">(</span><span class="pln">PWD</span><span class="pun">)</span><span class="pln"> modules</span></code></li><li class="L5"><code class="lang-makefile"></code></li><li class="L6"><code class="lang-makefile"><span class="pln">clean</span><span class="pun">:</span></code></li><li class="L7"><code class="lang-makefile"><span class="pln">        make </span><span class="pun">-</span><span class="pln">C $</span><span class="pun">(</span><span class="pln">BUILDPATH</span><span class="pun">)</span><span class="pln"> M</span><span class="pun">=</span><span class="pln">$</span><span class="pun">(</span><span class="pln">PWD</span><span class="pun">)</span><span class="pln"> clean</span></code></li></ol></pre>
      编译模块并将 .ko 文件复制到 initramfs，然后重新打包：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pln">$ make </span><span class="pun">&amp;&amp;</span><span class="pln"> cp hello</span><span class="pun">.</span><span class="pln">ko </span><span class="pun">~</span><span class="str">/kernelbuild/</span><span class="pln">busybox</span><span class="pun">-</span><span class="lit">1.28</span><span class="pun">.</span><span class="lit">3</span><span class="pun">/</span><span class="pln">initramfs</span></code></li><li class="L1"><code class="lang-text"><span class="pln">$ cd </span><span class="pun">~</span><span class="str">/kernelbuild/</span><span class="pln">busybox</span><span class="pun">-</span><span class="lit">1.28</span><span class="pun">.</span><span class="lit">3</span><span class="pun">/</span><span class="pln">initramfs</span></code></li><li class="L2"><code class="lang-text"><span class="pln">$ find </span><span class="pun">.</span><span class="pln"> </span><span class="pun">-</span><span class="pln">print0 </span><span class="pun">|</span><span class="pln"> cpio </span><span class="pun">--</span><span class="kwd">null</span><span class="pln"> </span><span class="pun">-</span><span class="pln">ov </span><span class="pun">--</span><span class="pln">format</span><span class="pun">=</span><span class="pln">newc </span><span class="pun">|</span><span class="pln"> gzip </span><span class="pun">-</span><span class="lit">9</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">../</span><span class="pln">initramfs</span><span class="pun">.</span><span class="pln">cpio</span><span class="pun">.</span><span class="pln">gz</span></code></li></ol></pre>
      最后重新启动 QEMU 即可：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="com"># insmod hello.ko</span></code></li><li class="L1"><code class="lang-text"><span class="pun">[</span><span class="pln">    </span><span class="lit">7.887392</span><span class="pun">]</span><span class="pln"> hello</span><span class="pun">:</span><span class="pln"> loading </span><span class="kwd">out</span><span class="pun">-</span><span class="pln">of</span><span class="pun">-</span><span class="pln">tree </span><span class="kwd">module</span><span class="pln"> taints kernel</span><span class="pun">.</span></code></li><li class="L2"><code class="lang-text"><span class="pun">[</span><span class="pln">    </span><span class="lit">7.892630</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Hello</span><span class="pln"> </span><span class="kwd">module</span><span class="pun">!</span></code></li><li class="L3"><code class="lang-text"><span class="com"># lsmod</span></code></li><li class="L4"><code class="lang-text"><span class="pln">hello </span><span class="lit">16384</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Live</span><span class="pln"> </span><span class="lit">0xffffffffa0000000</span><span class="pln"> </span><span class="pun">(</span><span class="pln">O</span><span class="pun">)</span></code></li><li class="L5"><code class="lang-text"><span class="com"># rmmod hello.ko</span></code></li><li class="L6"><code class="lang-text"><span class="pun">[</span><span class="pln">   </span><span class="lit">24.523830</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Goodbye</span><span class="pln"> </span><span class="kwd">module</span><span class="pun">!</span></code></li></ol></pre>
      三个命令分别用于载入、列出和卸载模块。
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="com"># cat /sys/module/hello/sections/.text</span></code></li><li class="L1"><code class="lang-text"><span class="lit">0x00000000fa16acc0</span></code></li></ol></pre>
      在这个例子中，只有 .text 段：
      <pre style="margin-left: 2em;"><ol class="linenums"><li class="L0"><code class="lang-text"><span class="pun">(</span><span class="pln">gdb</span><span class="pun">)</span><span class="pln"> add</span><span class="pun">-</span><span class="pln">symbol</span><span class="pun">-</span><span class="pln">file </span><span class="pun">~</span><span class="str">/kernelbuild/</span><span class="pln">busybox</span><span class="pun">-</span><span class="lit">1.28</span><span class="pun">.</span><span class="lit">3</span><span class="pun">/</span><span class="pln">initramfs</span><span class="pun">/</span><span class="pln">hello</span><span class="pun">.</span><span class="pln">ko </span><span class="lit">0x00000000fa16acc0</span></code></li></ol></pre>

      <h3>参考资料</h3>
      <ul><li style="margin-left: 2em;"><a href="https://wiki.ubuntu.com/Kernel/KernelDebuggingTricks" target="_blank">KernelDebuggingTricks</a></li></ul>
      <el-divider></el-divider>

      <h3>在 Linux 内核调试中，以下哪个工具通常用于跟踪和分析系统调用、中断和任务的执行情况？</h3>
      <el-radio-group v-model="radio">
        <el-radio :label="1">gdb</el-radio>
        <el-radio :label="2">valgrind</el-radio>
        <el-radio :label="3">strace</el-radio>
        <el-radio :label="4">tcpdump</el-radio>
        <el-button type="primary" plain :disabled="isButtonDisabled" @click="check(radio)">检查</el-button>
      </el-radio-group>
    </div>
    <el-divider></el-divider>

  </div>
</template>

<style scoped>

</style>